package cn.initcap.algorithm.swing.mazegenerator.randomup;

import cn.initcap.algorithm.swing.AlgoVisHelper;
import cn.initcap.algorithm.swing.BaseAlgoFrame;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

/**
 * @author initcap
 * @date 8/1/19 11:06 AM
 */
public class AlgoVisualizer {

    private static final int[][] D = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
    private static int DELAY = 5;
    private static int blockSide = 8;
    private MazeData data;
    private BaseAlgoFrame frame;

    public AlgoVisualizer(int n, int m) {

        // 初始化数据
        data = new MazeData(n, m);
        int sceneHeight = data.N() * blockSide;
        int sceneWidth = data.M() * blockSide;

        // 初始化视图
        EventQueue.invokeLater(() -> {
            frame = new BaseAlgoFrame("Random Maze Generation Visualization", sceneWidth, sceneHeight) {
                @Override
                public void draw(Graphics graphics) {
                    Graphics2D g2d = (Graphics2D) graphics;
                    int w = sceneWidth / data.M();
                    int h = sceneHeight / data.N();

                    for (int i = 0; i < data.N(); i++) {
                        for (int j = 0; j < data.M(); j++) {

                            if (data.inMist[i][j]) {
                                AlgoVisHelper.setColor(g2d, AlgoVisHelper.BLACK);
                            } else {
                                if (data.maze[i][j] == MazeData.WALL) {
                                    AlgoVisHelper.setColor(g2d, AlgoVisHelper.LIGHT_BLUE);
                                } else {
                                    AlgoVisHelper.setColor(g2d, AlgoVisHelper.WHITE);
                                }

                                if (data.path[i][j]) {
                                    AlgoVisHelper.setColor(g2d, AlgoVisHelper.YELLOW);
                                }
                            }

                            AlgoVisHelper.fillRectangle(g2d, j * w, i * h, w, h);
                        }
                    }
                }
            };
            frame.addKeyListener(new AlgoKeyListener());

            new Thread(() -> {
                run();
            }).start();
        });
    }

    public static void main(String[] args) {

        int n = 101;
        int m = 101;

        new AlgoVisualizer(n, m);

    }

    private void run() {

        setRoadData(-1, -1);

        RandomQueue<Position> queue = new RandomQueue<Position>();
        Position first = new Position(data.getEntranceX(), data.getEntranceY() + 1);
        queue.add(first);
        data.visited[first.getX()][first.getY()] = true;
        data.openMist(first.getX(), first.getY());

        while (queue.size() != 0) {
            Position curPos = queue.remove();

            for (int i = 0; i < 4; i++) {
                int newX = curPos.getX() + D[i][0] * 2;
                int newY = curPos.getY() + D[i][1] * 2;

                if (data.inArea(newX, newY) && !data.visited[newX][newY]) {
                    queue.add(new Position(newX, newY));
                    data.visited[newX][newY] = true;
                    data.openMist(newX, newY);
                    setRoadData(curPos.getX() + D[i][0], curPos.getY() + D[i][1]);
                }
            }
        }

        setRoadData(-1, -1);
    }

    private void setRoadData(int x, int y) {
        if (data.inArea(x, y)) {
            data.maze[x][y] = MazeData.ROAD;
        }

        frame.render(data);
        AlgoVisHelper.pause(DELAY);
    }

    private boolean go(int x, int y) {

        if (!data.inArea(x, y)) {
            throw new IllegalArgumentException("x,y are out of index in go function!");
        }

        data.visited[x][y] = true;
        setPathData(x, y, true);

        if (x == data.getExitX() && y == data.getExitY()) {
            return true;
        }

        for (int i = 0; i < 4; i++) {
            int newX = x + D[i][0];
            int newY = y + D[i][1];
            if (data.inArea(newX, newY) &&
                    data.maze[newX][newY] == MazeData.ROAD &&
                    !data.visited[newX][newY]) {
                if (go(newX, newY)) {
                    return true;
                }
            }
        }

        // 回溯
        setPathData(x, y, false);

        return false;
    }

    private void setPathData(int x, int y, boolean isPath) {
        if (data.inArea(x, y)) {
            data.path[x][y] = isPath;
        }

        frame.render(data);
        AlgoVisHelper.pause(DELAY);
    }

    private class AlgoKeyListener extends KeyAdapter {

        @Override
        public void keyReleased(KeyEvent event) {
            if (event.getKeyChar() == ' ') {
                for (int i = 0; i < data.N(); i++) {
                    for (int j = 0; j < data.M(); j++) {
                        data.visited[i][j] = false;
                    }
                }

                new Thread(() -> go(data.getEntranceX(), data.getEntranceY())).start();
            }
        }
    }
}